/*
 * Background Flickr: Copyright (C) 2009  Craig McNicholas
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.ddw.app.bflickr.flickr.api.url;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

import com.ddw.app.bflickr.flickr.api.FlickrURL;
import com.ddw.app.bflickr.logging.Log;
import com.ddw.app.bflickr.logging.LogFactory;

/**
 * Abstract Class to generate a flickr api url. This class is should not be used 
 * directly instead see the classes which inherit from it.
 * @author craig
 *
 */
public abstract class FlickrApiURL implements FlickrURL {
	
	/**
	 * The absolute flickr api url with formatting symbols
	 */
	public static final String FLICKR_API_URL = "http://api.flickr.com/services/rest/?method=%s&api_key=%s%s";
	
	/**
	 * Enumeration of the flickr api methods
	 * @author craig
	 */
	public static enum FlickrApiMethod {
	    FIND_BY_URL (),
	    FIND_BY_EMAIL (),
	    FIND_BY_USERNAME (),
	    GET_PUBLIC_PHOTOS ();

	    private Log log = LogFactory.createInstance(this.getClass());
	    
	    public String toString(){
	        switch(this) {
	            case FIND_BY_URL: return FlickrFindByWebsiteURL.SERVICE_METHOD;
	            case FIND_BY_EMAIL: return FlickrFindByEmailURL.SERVICE_METHOD;
	            case FIND_BY_USERNAME: return FlickrFindByUsernameURL.SERVICE_METHOD;
	            case GET_PUBLIC_PHOTOS: return FlickrGetPublicPhotosURL.SERVICE_METHOD;
	        }
	        log.error("Could not assert the value of the enumerated type " + this);
	        throw new AssertionError("Could not assert the value of the enumerated type " + this);
	    }
	};
	
	private Log log = LogFactory.createInstance(this.getClass());
	private FlickrApiMethod method = null;
	private String apiKey = null;
	private Map<String, String> extraParams = null;
	
	/**
	 * Creates a new flickr api url instance for using the REST api services 
	 * available at flickr.com
	 * @param method the api method to use
	 * @param apiKey the unique application key
	 * @param extraParams any extra query string parameters to pass to the service
	 * @throws IllegalArgumentException when the method or api key is null, <i>api methods and api keys cannot be null</i>
	 */
	public FlickrApiURL(FlickrApiMethod method, String apiKey, Map<String, String> extraParams) throws IllegalArgumentException {
		setMethod(method);
		setApiKey(apiKey);
		setExtraParams(extraParams);
	}
	
	/**
	 * Creates a new flickr api url instance for using the REST api services 
	 * available at flickr.com
	 * @param method the api method to use
	 * @param apiKey the unique application key
	 * @throws IllegalArgumentException when the method or api key is null, <i>api methods and api keys cannot be null</i>
	 */
	public FlickrApiURL(FlickrApiMethod method, String apiKey) throws IllegalArgumentException {
		setMethod(method);
		setApiKey(apiKey);
	}
	
	@Override
	public String getUrl() throws UnsupportedEncodingException {
		StringBuilder extra = new StringBuilder();
		if(this.extraParams != null) {
			log.debug("The url contains extra parameters, beginning parameter encoding.");
			for (String key : this.extraParams.keySet()) {
				String v = this.extraParams.get(key);
				String value = String.format("&%s=%s", encodeString(key), encodeString(v));
				log.debug("Encoded key: " + key + " value: " + v + " to: " + value);
				extra.append(value);
			}
		} else {
			log.warn("The url contains no extra parameters.");
		}
		return String.format(FLICKR_API_URL, encodeString(this.method.toString()), encodeString(this.apiKey), extra.toString());
	}
	
	/**
	 * Encodes a string value to a utf-8 url encoded safe value. <b>Following the RFC 3986 usage</b> 
	 * URLEncoder does not encode *, ~ or +, these have been added manually. <b><i>Further note: plus (+) 
	 * characters have been re-entered back into the string as flickr uses this format.</i></b>
	 * @param value the value to encode
	 * @return the utf-8 encoded value
	 * @throws UnsupportedEncodingException if the utf-8 encoding format is not supported
	 */
	private String encodeString(String value) throws UnsupportedEncodingException {
		try {
			return URLEncoder.encode(value, "UTF-8").replace("*", "%2A").replace("%7E", "~"); // .replace("+", "%20")
		} catch(UnsupportedEncodingException ex) {
			log.error("There was an error encoding the string '" + value + "'", ex);
			throw ex;
		}
	}
	
	/**
	 * Getter of the api method
	 * @return the api method to call
	 */
	public FlickrApiMethod getMethod() {
		return this.method;
	}
	
	/**
	 * Getter of the api key
	 * @return the unique api key for this product
	 */
	public String getApiKey() {
		return this.apiKey;
	}
	
	/**
	 * Getter of the extra parameters to attach to the query string.
	 * By default this value is null.
	 * @return the extra parameters
	 */
	public Map<String, String> getExtraParams() {
		if(this.extraParams == null) { log.warn("The extra parameters map is null."); }
		return this.extraParams;
	}
	
	/**
	 * Sets a parameter in the extra parameters map. If the map is empty or 
	 * null it will create a new hash map.
	 * @param key the key of the entry
	 * @param value the value of the entry
	 */
	protected void setExtraParam(String key, String value) {
		if(this.getExtraParams() == null) {
			log.debug("The extra parameters map was null, creating a new map instance.");
			this.setExtraParams(new HashMap<String, String>());
		}
		this.extraParams.put(key, value);
	}
	
	/**
	 * Checks if the extra parameters map contains a specific key. Also 
	 * first checks if the extra parameters map is null.
	 * @param key the key of the entry to find
	 */
	protected boolean hasExtraParam(String key) {
		if(this.getExtraParams() != null) {
			return this.getExtraParams().containsKey(key);
		}
		log.warn("The extra parameters map is null.");
		return false;
	}
	
	/**
	 * Setter of the api method
	 * @param method the api method to set
	 * @throws IllegalArgumentException when the method is null, <i>api methods cannot be null</i>
	 */
	protected void setMethod(FlickrApiMethod method) throws IllegalArgumentException {
		if(method == null) {
			log.error("The flickr api method cannot be null");
			throw new IllegalArgumentException("The flickr api method cannot be null");
		}
		this.method = method;
	}
	
	/**
	 * Setter of the api key
	 * @param apiKey the unique api key for this product
	 * @throws IllegalArgumentException when the api key is null, <i>api keys cannot be null</i>
	 */
	public void setApiKey(String apiKey) throws IllegalArgumentException {
		if(apiKey == null) {
			log.error("The flickr api key cannot be null");
			throw new IllegalArgumentException("The flickr api key cannot be null");
		}
		if(apiKey.trim().length() == 0) {
			log.error("The flickr api key cannot be empty");
			throw new IllegalArgumentException("The flickr api key cannot be empty");
		}
		this.apiKey = apiKey;
	}
	
	/**
	 * Setter of the extra parameters
	 * @param extraParams the extra parameters to attach to the query string
	 */
	protected void setExtraParams(Map<String, String> extraParams) {
		this.extraParams = extraParams;
	}

}
